Module 2: Data Visualization and Exploratory Analytics

Week 04: Exploratory Analytics and Visualization continued in Plotly + A/B Testing

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the ADVANCED ANALYST - Foundations for Advanced Data Analytics in R DataKolektiv training.


Welcome to R!

What do we want to do today?

Our task in this session is to learn the basics of the Plotly package, the industry standard in interactive data visualization. We will learn the basics of Plotly by diving even deeper into Exploratory Data Analysis of course! Interactive data visualizations are especially important today when means of delivering data analytics are mostly web-based and rarely based on print or slide-decks alone. One day, if you decide to continue your journey in R, you might realize just how powerful is the combination of Plotly with RStudio Shiny web applications..! We will also start introducing the concept of A/B testing in data analytics in this session, beginning with basic t-test for differences between means of two independent groups.

NOTE on the usage of pipe operator in this session: In this session, I will use the %>% pipe operator, popular in dplyr and originally developed in the magrittr package, in place of the previously used “new” native R pipe operator |>.

1. The data set at hand: Boston Housing Data

We will use the (in)famous Boston Housing Data data set in this session.

# Load the tidyverse package: ggplot2 is a part of it, but also
# Load plotly - the new data visualisation superstar!
library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.0     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     ── Conflicts ──────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(plotly)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
# The path to your CSV file
data_dir <- paste0(getwd(), "/_data/")
filename <- "BostonHousing.csv"
filepath <- paste0(data_dir, filename)

# Load the data into R
housing <- readr::read_csv(filepath)
Rows: 506 Columns: 14── Column specification ──────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (14): crim, zn, indus, chas, nox, rm, age, dis, rad, tax, ptratio, b, lstat, medv
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Glimpse its structure to ensure it has arrived in full
glimpse(housing)
Rows: 506
Columns: 14
$ crim    <dbl> 0.00632, 0.02731, 0.02729, 0.03237, 0.06905, 0.02985, 0.08829, 0.14455, 0.21…
$ zn      <dbl> 18.0, 0.0, 0.0, 0.0, 0.0, 0.0, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 0.0…
$ indus   <dbl> 2.31, 7.07, 7.07, 2.18, 2.18, 2.18, 7.87, 7.87, 7.87, 7.87, 7.87, 7.87, 7.87…
$ chas    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ nox     <dbl> 0.538, 0.469, 0.469, 0.458, 0.458, 0.458, 0.524, 0.524, 0.524, 0.524, 0.524,…
$ rm      <dbl> 6.575, 6.421, 7.185, 6.998, 7.147, 6.430, 6.012, 6.172, 5.631, 6.004, 6.377,…
$ age     <dbl> 65.2, 78.9, 61.1, 45.8, 54.2, 58.7, 66.6, 96.1, 100.0, 85.9, 94.3, 82.9, 39.…
$ dis     <dbl> 4.0900, 4.9671, 4.9671, 6.0622, 6.0622, 6.0622, 5.5605, 5.9505, 6.0821, 6.59…
$ rad     <dbl> 1, 2, 2, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4…
$ tax     <dbl> 296, 242, 242, 222, 222, 222, 311, 311, 311, 311, 311, 311, 311, 307, 307, 3…
$ ptratio <dbl> 15.3, 17.8, 17.8, 18.7, 18.7, 18.7, 15.2, 15.2, 15.2, 15.2, 15.2, 15.2, 15.2…
$ b       <dbl> 396.90, 396.90, 392.83, 394.63, 396.90, 394.12, 395.60, 396.90, 386.63, 386.…
$ lstat   <dbl> 4.98, 9.14, 4.03, 2.94, 5.33, 5.21, 12.43, 19.15, 29.93, 17.10, 20.45, 13.27…
$ medv    <dbl> 24.0, 21.6, 34.7, 33.4, 36.2, 28.7, 22.9, 27.1, 16.5, 18.9, 15.0, 18.9, 21.7…

Understanding the Boston Housing data:

  • crim: per capita crime rate by town
  • zn: proportion of residential land zoned for lots over 25,000 sq.ft.
  • indus: proportion of non-retail business acres per town.
  • chas: Charles River dummy variable (1 if tract bounds river; 0 otherwise)
  • nox: nitric oxides concentration (parts per 10 million)
  • rm: average number of rooms per dwelling
  • age: proportion of owner-occupied units built prior to 1940
  • dis: weighted distances to five Boston employment centers
  • rad: index of accessibility to radial highways
  • tax: full-value property-tax rate per $10,000
  • ptratio: pupil-teacher ratio by town
  • b: 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
  • lstat: % lower status of the population
  • medv: Median value of owner-occupied homes in $1000’s
# Generate summary statistics for the dataset
summary(housing)
      crim                zn             indus            chas              nox        
 Min.   : 0.00632   Min.   :  0.00   Min.   : 0.46   Min.   :0.00000   Min.   :0.3850  
 1st Qu.: 0.08205   1st Qu.:  0.00   1st Qu.: 5.19   1st Qu.:0.00000   1st Qu.:0.4490  
 Median : 0.25651   Median :  0.00   Median : 9.69   Median :0.00000   Median :0.5380  
 Mean   : 3.61352   Mean   : 11.36   Mean   :11.14   Mean   :0.06917   Mean   :0.5547  
 3rd Qu.: 3.67708   3rd Qu.: 12.50   3rd Qu.:18.10   3rd Qu.:0.00000   3rd Qu.:0.6240  
 Max.   :88.97620   Max.   :100.00   Max.   :27.74   Max.   :1.00000   Max.   :0.8710  
       rm             age              dis              rad              tax       
 Min.   :3.561   Min.   :  2.90   Min.   : 1.130   Min.   : 1.000   Min.   :187.0  
 1st Qu.:5.886   1st Qu.: 45.02   1st Qu.: 2.100   1st Qu.: 4.000   1st Qu.:279.0  
 Median :6.208   Median : 77.50   Median : 3.207   Median : 5.000   Median :330.0  
 Mean   :6.285   Mean   : 68.57   Mean   : 3.795   Mean   : 9.549   Mean   :408.2  
 3rd Qu.:6.623   3rd Qu.: 94.08   3rd Qu.: 5.188   3rd Qu.:24.000   3rd Qu.:666.0  
 Max.   :8.780   Max.   :100.00   Max.   :12.127   Max.   :24.000   Max.   :711.0  
    ptratio            b              lstat            medv      
 Min.   :12.60   Min.   :  0.32   Min.   : 1.73   Min.   : 5.00  
 1st Qu.:17.40   1st Qu.:375.38   1st Qu.: 6.95   1st Qu.:17.02  
 Median :19.05   Median :391.44   Median :11.36   Median :21.20  
 Mean   :18.46   Mean   :356.67   Mean   :12.65   Mean   :22.53  
 3rd Qu.:20.20   3rd Qu.:396.23   3rd Qu.:16.95   3rd Qu.:25.00  
 Max.   :22.00   Max.   :396.90   Max.   :37.97   Max.   :50.00  

2. Relationship Between Number of Rooms and Home Value

EDA Question: How does the number of rooms (rm) influence the median value of homes (medv)?

Creating a Scatter Plot in Plotly

Step-by-step Explanation:

Initialize the Plot: We start by calling plot_ly(), where we specify the dataset and the axes.

plot_ly(data = housing, x = ~rm, y = ~medv)
  • data = housing tells Plotly to use the Boston Housing dataset;
  • x = ~rm sets the x-axis to represent the number of rooms;
  • y = ~medv sets the y-axis to represent the median home value.

This is similar to ggplot2::aes() mapping, right?

Now we want to specify the Plot Type: define the plot type and mode.

This is what we will do:

type = 'scatter', mode = 'markers'
  • type = 'scatter' creates a scatter plot;
  • mode = 'markers' uses points to represent data points.

Next, customize Markers: adjust the appearance of the markers.

marker = list(size = 10, color = 'rgba(35, 107, 195, 0.64)')
  • size = 10 makes each marker size consistent;
  • color = 'rgba(255, 182, 193, .9) sets a light pink color with some transparenc;

Add text labels: include text labels that appear when hovering over each marker.

text = ~paste("MEDV:", medv, "Rooms:", rm)
  • text = configures the hover text to show both the median value and number of rooms.

Finally, we configure the Layout: finalize the plot with titles and axis labels.

layout(title = "Scatter Plot of Median Home Value vs. Number of Rooms",
       xaxis = list(title = "Number of Rooms"),
       yaxis = list(title = "Median Home Value ($1000s)"))
  • title sets the main title of the plot;
  • xaxis and yaxis define the titles for the x-axis and y-axis, respectively.

Here is the complete code for our Plotly interactive scatter plot:

plot_ly(data = housing, 
        x = ~rm, 
        y = ~medv, 
        type = 'scatter', 
        mode = 'markers',
        marker = list(size = 10, color = 'rgba(255, 182, 193, .9)'), 
        text = ~paste("MEDV:", medv, "Rooms:", rm)) %>%
  layout(title = "Scatter Plot of Median Home Value vs. Number of Rooms",
         xaxis = list(title = "Number of Rooms"),
         yaxis = list(title = "Median Home Value ($1000s)"))

Analysis. This scatter plot helps us visually assess the relationship between the number of rooms and home values. Generally, we might observe a positive trend where more rooms indicate a higher median home value, suggesting that larger homes in Boston are more expensive.

3. Distribution of Property Tax Rates

EDA Question. What is the variability and distribution of property tax rates (tax) across different properties?

Creating a Histogram in Plotly

First the complete code, and then a step-by-step explanation:

plot_ly(housing, 
        x = ~tax, 
        type = "histogram",
        marker = list(color = 'rgba(100, 250, 100, 0.7)')) %>%
  layout(title = "Histogram of Property Tax Rates",
         xaxis = list(title = "Property Tax Rate"),
         yaxis = list(title = "Count"))

Initialize the plot:

plot_ly(boston_housing, x = ~tax)
  • x = ~tax sets the x-axis to represent the property tax rates.

Specify the plot type: define that you want to create a histogram.

type = "histogram"
  • type = "histogram" tells Plotly to generate a histogram.

Customize appearance: customize the color and style of the histogram bars.

marker = list(color = 'rgba(100, 250, 100, 0.7)')
  • color = 'rgba(100, 250, 100, 0.7)' sets a semi-transparent green color.

Configure the Layout: Add titles and axis labels to make the plot informative.

layout(title = "Histogram of Property Tax Rates",
       xaxis = list(title = "Property Tax Rate"),
       yaxis = list(title = "Frequency"))
  • title provides a main title.
  • xaxis and yaxis define the labels for the x-axis and y-axis.

Analysis. The histogram reveals the distribution of property tax rates among properties in Boston. It helps identify the most common tax rates and detect any outliers or unusual spikes in the data.

4. Crime Rate, Age, and Median Value

EDA Question. How does the crime crim rate correlate with age, and how are these factors related to whether the property tract bounds Charles River or not (see: chas variable)?

Creating a Bubble Chart in Plotly

Here is the complete code:

plot_ly(data = housing, 
        x = ~age, 
        y = ~crim, 
        type = 'scatter', 
        mode = 'markers',
        marker = list(size = ~medv/3,
                      color = ~chas),
        text = ~paste("Crime Rate:", crim, 
                      "<br>Age:", age, 
                      "<br>Median Value:", medv,
                      "<br>Charles River:", chas)) %>%
  layout(title = "Bubble Chart of Crime Rate vs. Age",
         xaxis = list(title = "Age"),
         yaxis = list(title = "Crime Rate"))
NA

Log-scales for both crim and age might be of some help here:

plot_ly(data = housing, 
        x = ~log(age), 
        y = ~log(crim), 
        type = 'scatter', 
        mode = 'markers',
        marker = list(size = ~medv/3,
                      color = ~chas),
        text = ~paste("Crime Rate:", crim, 
                      "<br>Age:", age, 
                      "<br>Median Value:", medv,
                      "<br>Charles River:", chas)) %>%
  layout(title = "Bubble Chart of Crime Rate vs. Age",
         xaxis = list(title = "log(Age)"),
         yaxis = list(title = "log(Crime Rate)"))

Analysis. The highest crime rates are indeed found in the areas where oldest properties are situated which seem to be the cheapest at the same time. There is no apparent evidence whether bounding the Charles River banks make any difference

5. Difference between type and mode and their usage in plot_ly()

In Plotly, particularly when using the plot_ly() function in R, the type and mode parameters serve distinct roles in defining how data is visualized:

type parameter

  • Definition: The type parameter in plot_ly() specifies the type of plot or chart that you want to create. This determines the overall visual representation of the data.

  • Common Types: These include scatter, bar, box, histogram, heatmap, surface, scatter3d, mesh3d, and others.

  • Usage Example: If you want to create a line plot, you would set type = 'scatter' and then specify mode = 'lines' (explained below under mode). For a simple bar chart, you would use type = 'bar'.

mode parameter

  • Definition: The mode parameter is used primarily with scatter plots (including line charts and bubble charts) to define how the data points are connected or represented visually within the given type.

  • Common Modes: For type = 'scatter', common mode values include:

    • markers: Displays data points as individual markers (dots).
    • lines: Connects data points with lines.
    • text: Displays data points as text labels.
    • lines+markers: Uses both lines and markers to represent data points.
    • lines+markers+text: Combines lines, markers, and text to display the data.
  • Usage Example: To create a plot that shows both the data points and the lines connecting them, you would use plot_ly(type = 'scatter', mode = 'lines+markers').

Example in R

Here’s a simple example demonstrating the use of type and mode in plot_ly():

# Sample data
data <- data.frame(
  x = 1:100,
  y = sort(runif(100, -50, 50))
)

# Line plot
plot_ly(data, 
        x = ~x, 
        y = ~y, 
        type = 'scatter', 
        mode = 'lines') %>%
  layout(title = 'Line Plot')
# Scatter plot with markers
plot_ly(data, 
        x = ~x, 
        y = ~y, 
        type = 'scatter', 
        mode = 'markers') %>%
  layout(title = 'Scatter Plot with Markers')
# Scatter plot with markers and lines
plot_ly(data, 
        x = ~x, 
        y = ~y, 
        type = 'scatter', 
        mode = 'markers+lines') %>%
  layout(title = 'Scatter Plot with Markers and Lines')

For a simple bar plot we can use type='bar' with mode='lines':

# Scatter plot with bars
plot_ly(data, 
        x = ~x, 
        y = ~y, 
        type = 'bar', 
        mode = 'lines') %>%
  layout(title = 'Simple Bat Plot')

6. A practical introduction to A/B testing

6.1 Testing the difference between two means: t-test

The t-test is a statistical test that is used to determine whether there is a significant difference between the means of two groups. It is commonly used when you want to compare the average performance, measurements, or outcomes between two groups under different conditions or treatments. The t-test provides a way to check if the differences observed in the data are likely to be genuinely reflecting a difference in the population, or if they could just be due to random variation.

There are several types of t-tests, but the most common are:

  • Independent samples t-test. This is used when comparing two different groups of subjects (e.g., people receiving two different types of treatment).
  • Paired sample t-test. This is used when comparing two values from the same group at different times (e.g., before and after treatment for the same group of patients).

For our analysis, we’ll focus on the independent samples t-test as we are comparing two different subsets of data:

  • Q. Is there a statistically significant difference in the median values of homes between areas with crime rates above the median compared to those with crime rates at or below the median in the Boston housing market?

Let’s move through the steps of our analysis:

  • Calculate the median of the crim (crime rate) variable.
  • Split the data set based on whether crim is greater than or less than or equal to its median.
  • Conduct an independent t-test on the medv (median value of owner-occupied homes) between the two groups.
  • Report and interpret the results.

First, we will load the data, calculate the necessary statistics, and perform the t-test.

Find the median of crim:

# Calculate the median of the 'crim' variable
median_crim <- median(housing$crim)
print(median_crim)
[1] 0.25651

Use dplyr to create two new data sets that we need:

# Subset the data into two groups
low_crim <- housing %>% 
  dplyr::filter(crim <= median_crim) %>% 
  dplyr::select(crim, medv)

high_crim <- housing %>% 
  dplyr::filter(crim > median_crim) %>% 
  dplyr::select(crim, medv)

head(low_crim)
head(high_crim)

Perform t-test in R using t.test():

# Perform an independent t-test on 'medv'
t_test_results <- t.test(x = low_crim$medv, 
                         y = high_crim$medv,
                         # var.equal = TRUE
                         )

# Print the results
print(t_test_results)

    Welch Two Sample t-test

data:  low_crim$medv and high_crim$medv
t = 6.1202, df = 452.59, p-value = 2.027e-09
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 3.281241 6.385162
sample estimates:
mean of x mean of y 
 24.94941  20.11621 

This code will output the results of the t-test, which includes the t-statistic, degrees of freedom, p-value, and confidence interval of the difference in means.

Interpretation:

  • T-statistic: This value indicates the calculated difference in means between the two groups (low crime rate and high crime rate areas) relative to the spread or variability of their scores. A higher t-statistic indicates a greater difference between groups.

  • P-value: The p-value is extremely small, far less than 0.05, which is a common threshold for statistical significance in social sciences. This suggests that the differences in median home values between areas with crime rates higher than the median and those with crime rates lower or equal to the median are statistically significant.

Conclusion:

Based on the t-test, we can conclude that there is a statistically significant difference in the median values of homes between areas with relatively higher crime rates compared to those with lower crime rates. This implies that, on average, areas with lower crime rates tend to have higher median home values. This result is quite intuitive as safer neighborhoods are generally more desirable and can drive higher property values. To illustrate:

housing %>% 
  dplyr::select(crim, medv) %>% 
  dplyr::mutate(med_crim = crim > median(crim)) %>% 
  dplyr::select(med_crim, medv) %>% 
  dplyr::group_by(med_crim) %>% 
  dplyr::summarise(mean_medv = mean(medv), 
                   median_medv = median(medv))

R Markdown

R Markdown is what I have used to produce this beautiful Notebook. We will learn more about it near the end of the course, but if you already feel ready to dive deep, here’s a book: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds.


Goran S. Milovanović

DataKolektiv, 2024.

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tDQp0aXRsZTogIkFEVkFOQ0VEIEFOQUxZU1QgLSBGb3VuZGF0aW9ucyBmb3IgQWR2YW5jZWQgRGF0YSBBbmFseXRpY3MgaW4gUiAtIFNlc3Npb24wNCINCmF1dGhvcjoNCi0gbmFtZTogR29yYW4gUy4gTWlsb3Zhbm92acSHLCBQaEQNCiAgYWZmaWxpYXRpb246IERhdGFLb2xla3RpdiwgQ2hpZWYgU2NpZW50aXN0ICYgT3duZXI7IExlYWQgRGF0YSBTY2llbnRpc3QgZm9yIHNtYXJ0b2N0bw0KYWJzdHJhY3Q6IG51bGwNCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0b2NfZGVwdGg6IDUNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDUNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogJzUnDQotLS0NCg0KIVtdKF9pbWcvREtfTG9nb18xMDAucG5nKQ0KDQoqKioNCiMgTW9kdWxlIDI6IERhdGEgVmlzdWFsaXphdGlvbiBhbmQgRXhwbG9yYXRvcnkgQW5hbHl0aWNzDQoNCiMjIFdlZWsgMDQ6IEV4cGxvcmF0b3J5IEFuYWx5dGljcyBhbmQgVmlzdWFsaXphdGlvbiBjb250aW51ZWQgaW4gUGxvdGx5ICsgQS9CIFRlc3RpbmcNCg0KKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tYC4gDQpUaGVzZSBub3RlYm9va3MgYWNjb21wYW55IHRoZSBBRFZBTkNFRCBBTkFMWVNUIC0gRm91bmRhdGlvbnMgZm9yIEFkdmFuY2VkIERhdGEgQW5hbHl0aWNzIGluIFIgW0RhdGFLb2xla3Rpdl0oaHR0cDovL3d3dy5kYXRha29sZWt0aXYuY29tL2FwcF9kaXJlY3QvRGF0YUtvbGVrdGl2U2VydmVyLykgdHJhaW5pbmcuDQoNCioqKg0KDQojIyMgV2VsY29tZSB0byBSIQ0KDQohW10oX2ltZy9BZHZBbmFseXRpY3NSMjAyNF9CYW5uZXIuanBlZykNCg0KIyMjIFdoYXQgZG8gd2Ugd2FudCB0byBkbyB0b2RheT8NCg0KT3VyIHRhc2sgaW4gdGhpcyBzZXNzaW9uIGlzIHRvIGxlYXJuIHRoZSBiYXNpY3Mgb2YgdGhlIFtQbG90bHldKGh0dHBzOi8vcGxvdGx5LmNvbS9yLykgcGFja2FnZSwgdGhlIGluZHVzdHJ5IHN0YW5kYXJkIGluICppbnRlcmFjdGl2ZSBkYXRhIHZpc3VhbGl6YXRpb24qLiBXZSB3aWxsIGxlYXJuIHRoZSBiYXNpY3Mgb2YgUGxvdGx5IGJ5IGRpdmluZyBldmVuIGRlZXBlciBpbnRvICoqRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyoqIG9mIGNvdXJzZSEgSW50ZXJhY3RpdmUgZGF0YSB2aXN1YWxpemF0aW9ucyBhcmUgZXNwZWNpYWxseSBpbXBvcnRhbnQgdG9kYXkgd2hlbiBtZWFucyBvZiBkZWxpdmVyaW5nIGRhdGEgYW5hbHl0aWNzIGFyZSBtb3N0bHkgd2ViLWJhc2VkIGFuZCByYXJlbHkgYmFzZWQgb24gcHJpbnQgb3Igc2xpZGUtZGVja3MgYWxvbmUuIE9uZSBkYXksIGlmIHlvdSBkZWNpZGUgdG8gY29udGludWUgeW91ciBqb3VybmV5IGluIFIsIHlvdSBtaWdodCByZWFsaXplIGp1c3QgaG93IHBvd2VyZnVsIGlzIHRoZSBjb21iaW5hdGlvbiBvZiBQbG90bHkgd2l0aCBSU3R1ZGlvIFtTaGlueSB3ZWIgYXBwbGljYXRpb25zXShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS9wcm9kdWN0cy9zaGlueS8pLi4hIFdlIHdpbGwgYWxzbyBzdGFydCBpbnRyb2R1Y2luZyB0aGUgY29uY2VwdCBvZiBBL0IgdGVzdGluZyBpbiBkYXRhIGFuYWx5dGljcyBpbiB0aGlzIHNlc3Npb24sIGJlZ2lubmluZyB3aXRoIGJhc2ljIHQtdGVzdCBmb3IgZGlmZmVyZW5jZXMgYmV0d2VlbiBtZWFucyBvZiB0d28gaW5kZXBlbmRlbnQgZ3JvdXBzLg0KDQoqKk5PVEUgb24gdGhlIHVzYWdlIG9mIHBpcGUgb3BlcmF0b3IgaW4gdGhpcyBzZXNzaW9uOioqIEluIHRoaXMgc2Vzc2lvbiwgSSB3aWxsIHVzZSB0aGUgYCU+JWAgcGlwZSBvcGVyYXRvciwgcG9wdWxhciBpbiBbZHBseXJdKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy8pIGFuZCBvcmlnaW5hbGx5IGRldmVsb3BlZCBpbiB0aGUgIFttYWdyaXR0cl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL21hZ3JpdHRyL3ZpZ25ldHRlcy9tYWdyaXR0ci5odG1sKSBwYWNrYWdlLCBpbiBwbGFjZSBvZiB0aGUgcHJldmlvdXNseSB1c2VkICJuZXciIG5hdGl2ZSBSIHBpcGUgb3BlcmF0b3IgYHw+YC4gDQoNCiMjIyAxLiBUaGUgZGF0YSBzZXQgYXQgaGFuZDogYEJvc3RvbiBIb3VzaW5nIERhdGFgIA0KDQpXZSB3aWxsIHVzZSB0aGUgKGluKWZhbW91cyBbQm9zdG9uIEhvdXNpbmcgRGF0YSBkYXRhIHNldF0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc2V0cy9zY2hpcm1lcmNoYWQvYm9zdG9uaG91c3RpbmdtbG5kKSBpbiB0aGlzIHNlc3Npb24uDQoNCmBgYHtyIGVjaG8gPSBUfQ0KIyBMb2FkIHRoZSB0aWR5dmVyc2UgcGFja2FnZTogZ2dwbG90MiBpcyBhIHBhcnQgb2YgaXQsIGJ1dCBhbHNvDQojIExvYWQgcGxvdGx5IC0gdGhlIG5ldyBkYXRhIHZpc3VhbGlzYXRpb24gc3VwZXJzdGFyIQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHBsb3RseSkNCg0KIyBUaGUgcGF0aCB0byB5b3VyIENTViBmaWxlDQpkYXRhX2RpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi9fZGF0YS8iKQ0KZmlsZW5hbWUgPC0gIkJvc3RvbkhvdXNpbmcuY3N2Ig0KZmlsZXBhdGggPC0gcGFzdGUwKGRhdGFfZGlyLCBmaWxlbmFtZSkNCg0KIyBMb2FkIHRoZSBkYXRhIGludG8gUg0KaG91c2luZyA8LSByZWFkcjo6cmVhZF9jc3YoZmlsZXBhdGgpDQoNCiMgR2xpbXBzZSBpdHMgc3RydWN0dXJlIHRvIGVuc3VyZSBpdCBoYXMgYXJyaXZlZCBpbiBmdWxsDQpnbGltcHNlKGhvdXNpbmcpDQpgYGANCg0KVW5kZXJzdGFuZGluZyB0aGUgYEJvc3RvbiBIb3VzaW5nYCBkYXRhOg0KDQotIGBjcmltYDogcGVyIGNhcGl0YSBjcmltZSByYXRlIGJ5IHRvd24NCi0gYHpuYDogcHJvcG9ydGlvbiBvZiByZXNpZGVudGlhbCBsYW5kIHpvbmVkIGZvciBsb3RzIG92ZXIgMjUsMDAwIHNxLmZ0Lg0KLSBgaW5kdXNgOiBwcm9wb3J0aW9uIG9mIG5vbi1yZXRhaWwgYnVzaW5lc3MgYWNyZXMgcGVyIHRvd24uDQotIGBjaGFzYDogQ2hhcmxlcyBSaXZlciBkdW1teSB2YXJpYWJsZSAoMSBpZiB0cmFjdCBib3VuZHMgcml2ZXI7IDAgb3RoZXJ3aXNlKQ0KLSBgbm94YDogbml0cmljIG94aWRlcyBjb25jZW50cmF0aW9uIChwYXJ0cyBwZXIgMTAgbWlsbGlvbikNCi0gYHJtYDogYXZlcmFnZSBudW1iZXIgb2Ygcm9vbXMgcGVyIGR3ZWxsaW5nDQotIGBhZ2VgOiBwcm9wb3J0aW9uIG9mIG93bmVyLW9jY3VwaWVkIHVuaXRzIGJ1aWx0IHByaW9yIHRvIDE5NDANCi0gYGRpc2A6IHdlaWdodGVkIGRpc3RhbmNlcyB0byBmaXZlIEJvc3RvbiBlbXBsb3ltZW50IGNlbnRlcnMNCi0gYHJhZGA6IGluZGV4IG9mIGFjY2Vzc2liaWxpdHkgdG8gcmFkaWFsIGhpZ2h3YXlzDQotIGB0YXhgOiBmdWxsLXZhbHVlIHByb3BlcnR5LXRheCByYXRlIHBlciAkMTAsMDAwDQotIGBwdHJhdGlvYDogcHVwaWwtdGVhY2hlciByYXRpbyBieSB0b3duDQotIGBiYDogMTAwMChCayAtIDAuNjMpXjIgd2hlcmUgQmsgaXMgdGhlIHByb3BvcnRpb24gb2YgYmxhY2tzIGJ5IHRvd24NCi0gYGxzdGF0YDogJSBsb3dlciBzdGF0dXMgb2YgdGhlIHBvcHVsYXRpb24NCi0gYG1lZHZgOiBNZWRpYW4gdmFsdWUgb2Ygb3duZXItb2NjdXBpZWQgaG9tZXMgaW4gJDEwMDDigJlzDQoNCg0KYGBge3IgZWNobyA9IFR9DQojIEdlbmVyYXRlIHN1bW1hcnkgc3RhdGlzdGljcyBmb3IgdGhlIGRhdGFzZXQNCnN1bW1hcnkoaG91c2luZykNCmBgYA0KDQojIyMgMi4gUmVsYXRpb25zaGlwIEJldHdlZW4gTnVtYmVyIG9mIFJvb21zIGFuZCBIb21lIFZhbHVlDQoNCioqRURBIFF1ZXN0aW9uOioqIEhvdyBkb2VzIHRoZSBudW1iZXIgb2Ygcm9vbXMgKGBybWApIGluZmx1ZW5jZSB0aGUgbWVkaWFuIHZhbHVlIG9mIGhvbWVzIChgbWVkdmApPw0KDQojIyMjIENyZWF0aW5nIGEgU2NhdHRlciBQbG90IGluIFBsb3RseQ0KDQpTdGVwLWJ5LXN0ZXAgRXhwbGFuYXRpb246DQoNCkluaXRpYWxpemUgdGhlIFBsb3Q6IFdlIHN0YXJ0IGJ5IGNhbGxpbmcgcGxvdF9seSgpLCB3aGVyZSB3ZSBzcGVjaWZ5IHRoZSBkYXRhc2V0IGFuZCB0aGUgYXhlcy4NCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcGxvdF9seShkYXRhID0gaG91c2luZywgeCA9IH5ybSwgeSA9IH5tZWR2KQ0KYGBgDQoNCi0gYGRhdGEgPSBob3VzaW5nYCB0ZWxscyBQbG90bHkgdG8gdXNlIHRoZSBCb3N0b24gSG91c2luZyBkYXRhc2V0Ow0KLSBgeCA9IH5ybWAgc2V0cyB0aGUgeC1heGlzIHRvIHJlcHJlc2VudCB0aGUgbnVtYmVyIG9mIHJvb21zOw0KLSBgeSA9IH5tZWR2YCBzZXRzIHRoZSB5LWF4aXMgdG8gcmVwcmVzZW50IHRoZSBtZWRpYW4gaG9tZSB2YWx1ZS4NCg0KVGhpcyBpcyBzaW1pbGFyIHRvIGBnZ3Bsb3QyOjphZXMoKWAgbWFwcGluZywgcmlnaHQ/DQoNCk5vdyB3ZSB3YW50IHRvIHNwZWNpZnkgdGhlIFBsb3QgVHlwZTogZGVmaW5lIHRoZSBwbG90IHR5cGUgYW5kIG1vZGUuDQoNClRoaXMgaXMgd2hhdCB3ZSB3aWxsIGRvOg0KDQpgYGANCnR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbWFya2VycycNCmBgYA0KDQotIGB0eXBlID0gJ3NjYXR0ZXInYCBjcmVhdGVzIGEgc2NhdHRlciBwbG90Ow0KLSBgbW9kZSA9ICdtYXJrZXJzJ2AgdXNlcyBwb2ludHMgdG8gcmVwcmVzZW50IGRhdGEgcG9pbnRzLg0KDQoNCk5leHQsIGN1c3RvbWl6ZSBNYXJrZXJzOiBhZGp1c3QgdGhlIGFwcGVhcmFuY2Ugb2YgdGhlIG1hcmtlcnMuDQoNCmBgYA0KbWFya2VyID0gbGlzdChzaXplID0gMTAsIGNvbG9yID0gJ3JnYmEoMzUsIDEwNywgMTk1LCAwLjY0KScpDQpgYGANCg0KLSBgc2l6ZSA9IDEwYCBtYWtlcyBlYWNoIG1hcmtlciBzaXplIGNvbnNpc3RlbnQ7DQotIGBjb2xvciA9ICdyZ2JhKDI1NSwgMTgyLCAxOTMsIC45KWAgc2V0cyBhIGxpZ2h0IHBpbmsgY29sb3Igd2l0aCBzb21lIHRyYW5zcGFyZW5jOw0KDQpBZGQgdGV4dCBsYWJlbHM6IGluY2x1ZGUgdGV4dCBsYWJlbHMgdGhhdCBhcHBlYXIgd2hlbiBob3ZlcmluZyBvdmVyIGVhY2ggbWFya2VyLg0KDQpgYGANCnRleHQgPSB+cGFzdGUoIk1FRFY6IiwgbWVkdiwgIlJvb21zOiIsIHJtKQ0KYGBgDQoNCi0gYHRleHQgPSBgIGNvbmZpZ3VyZXMgdGhlIGhvdmVyIHRleHQgdG8gc2hvdyBib3RoIHRoZSBtZWRpYW4gdmFsdWUgYW5kIG51bWJlciBvZiByb29tcy4NCg0KRmluYWxseSwgd2UgY29uZmlndXJlIHRoZSBMYXlvdXQ6IGZpbmFsaXplIHRoZSBwbG90IHdpdGggdGl0bGVzIGFuZCBheGlzIGxhYmVscy4NCg0KYGBgDQpsYXlvdXQodGl0bGUgPSAiU2NhdHRlciBQbG90IG9mIE1lZGlhbiBIb21lIFZhbHVlIHZzLiBOdW1iZXIgb2YgUm9vbXMiLA0KICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJOdW1iZXIgb2YgUm9vbXMiKSwNCiAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTWVkaWFuIEhvbWUgVmFsdWUgKCQxMDAwcykiKSkNCmBgYA0KDQotIGB0aXRsZWAgc2V0cyB0aGUgbWFpbiB0aXRsZSBvZiB0aGUgcGxvdDsNCi0gYHhheGlzYCBhbmQgYHlheGlzYCBkZWZpbmUgdGhlIHRpdGxlcyBmb3IgdGhlIHgtYXhpcyBhbmQgeS1heGlzLCByZXNwZWN0aXZlbHkuDQoNCioqSGVyZSBpcyB0aGUgY29tcGxldGUgY29kZSBmb3Igb3VyIFBsb3RseSBpbnRlcmFjdGl2ZSBzY2F0dGVyIHBsb3Q6KioNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcGxvdF9seShkYXRhID0gaG91c2luZywgDQogICAgICAgIHggPSB+cm0sIA0KICAgICAgICB5ID0gfm1lZHYsIA0KICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCANCiAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywNCiAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gMTAsIGNvbG9yID0gJ3JnYmEoMjU1LCAxODIsIDE5MywgLjkpJyksIA0KICAgICAgICB0ZXh0ID0gfnBhc3RlKCJNRURWOiIsIG1lZHYsICJSb29tczoiLCBybSkpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiU2NhdHRlciBQbG90IG9mIE1lZGlhbiBIb21lIFZhbHVlIHZzLiBOdW1iZXIgb2YgUm9vbXMiLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIk51bWJlciBvZiBSb29tcyIpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIk1lZGlhbiBIb21lIFZhbHVlICgkMTAwMHMpIikpDQpgYGANCg0KKipBbmFseXNpcy4qKiBUaGlzIHNjYXR0ZXIgcGxvdCBoZWxwcyB1cyB2aXN1YWxseSBhc3Nlc3MgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBudW1iZXIgb2Ygcm9vbXMgYW5kIGhvbWUgdmFsdWVzLiBHZW5lcmFsbHksIHdlIG1pZ2h0IG9ic2VydmUgYSBwb3NpdGl2ZSB0cmVuZCB3aGVyZSBtb3JlIHJvb21zIGluZGljYXRlIGEgaGlnaGVyIG1lZGlhbiBob21lIHZhbHVlLCBzdWdnZXN0aW5nIHRoYXQgbGFyZ2VyIGhvbWVzIGluIEJvc3RvbiBhcmUgbW9yZSBleHBlbnNpdmUuDQoNCiMjIyAzLiBEaXN0cmlidXRpb24gb2YgUHJvcGVydHkgVGF4IFJhdGVzDQoNCioqRURBIFF1ZXN0aW9uLioqIFdoYXQgaXMgdGhlIHZhcmlhYmlsaXR5IGFuZCBkaXN0cmlidXRpb24gb2YgcHJvcGVydHkgdGF4IHJhdGVzICh0YXgpIGFjcm9zcyBkaWZmZXJlbnQgcHJvcGVydGllcz8NCg0KIyMjIyBDcmVhdGluZyBhIEhpc3RvZ3JhbSBpbiBQbG90bHkNCg0KRmlyc3QgdGhlIGNvbXBsZXRlIGNvZGUsIGFuZCB0aGVuIGEgc3RlcC1ieS1zdGVwIGV4cGxhbmF0aW9uOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpwbG90X2x5KGhvdXNpbmcsIA0KICAgICAgICB4ID0gfnRheCwgDQogICAgICAgIHR5cGUgPSAiaGlzdG9ncmFtIiwNCiAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICdyZ2JhKDEwMCwgMjUwLCAxMDAsIDAuNyknKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICJIaXN0b2dyYW0gb2YgUHJvcGVydHkgVGF4IFJhdGVzIiwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJQcm9wZXJ0eSBUYXggUmF0ZSIpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkNvdW50IikpDQpgYGANCg0KDQpJbml0aWFsaXplIHRoZSBwbG90OiANCg0KYGBgDQpwbG90X2x5KGJvc3Rvbl9ob3VzaW5nLCB4ID0gfnRheCkNCmBgYA0KDQotIGB4ID0gfnRheGAgc2V0cyB0aGUgeC1heGlzIHRvIHJlcHJlc2VudCB0aGUgcHJvcGVydHkgdGF4IHJhdGVzLg0KDQpTcGVjaWZ5IHRoZSBwbG90IHR5cGU6IGRlZmluZSB0aGF0IHlvdSB3YW50IHRvIGNyZWF0ZSBhIGhpc3RvZ3JhbS4NCg0KYGBgDQp0eXBlID0gImhpc3RvZ3JhbSINCmBgYA0KDQotIGB0eXBlID0gImhpc3RvZ3JhbSJgIHRlbGxzIFBsb3RseSB0byBnZW5lcmF0ZSBhIGhpc3RvZ3JhbS4NCg0KQ3VzdG9taXplIGFwcGVhcmFuY2U6IGN1c3RvbWl6ZSB0aGUgY29sb3IgYW5kIHN0eWxlIG9mIHRoZSBoaXN0b2dyYW0gYmFycy4NCg0KDQpgYGANCm1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiYSgxMDAsIDI1MCwgMTAwLCAwLjcpJykNCmBgYA0KDQotIGBjb2xvciA9ICdyZ2JhKDEwMCwgMjUwLCAxMDAsIDAuNyknYCBzZXRzIGEgc2VtaS10cmFuc3BhcmVudCBncmVlbiBjb2xvci4NCg0KQ29uZmlndXJlIHRoZSBMYXlvdXQ6IEFkZCB0aXRsZXMgYW5kIGF4aXMgbGFiZWxzIHRvIG1ha2UgdGhlIHBsb3QgaW5mb3JtYXRpdmUuDQoNCmBgYA0KbGF5b3V0KHRpdGxlID0gIkhpc3RvZ3JhbSBvZiBQcm9wZXJ0eSBUYXggUmF0ZXMiLA0KICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJQcm9wZXJ0eSBUYXggUmF0ZSIpLA0KICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJGcmVxdWVuY3kiKSkNCmBgYA0KDQotIGB0aXRsZWAgcHJvdmlkZXMgYSBtYWluIHRpdGxlLg0KLSBgeGF4aXNgIGFuZCBgeWF4aXNgIGRlZmluZSB0aGUgbGFiZWxzIGZvciB0aGUgeC1heGlzIGFuZCB5LWF4aXMuDQoNCioqQW5hbHlzaXMuKiogVGhlIGhpc3RvZ3JhbSByZXZlYWxzIHRoZSBkaXN0cmlidXRpb24gb2YgcHJvcGVydHkgdGF4IHJhdGVzIGFtb25nIHByb3BlcnRpZXMgaW4gQm9zdG9uLiBJdCBoZWxwcyBpZGVudGlmeSB0aGUgbW9zdCBjb21tb24gdGF4IHJhdGVzIGFuZCBkZXRlY3QgYW55IG91dGxpZXJzIG9yIHVudXN1YWwgc3Bpa2VzIGluIHRoZSBkYXRhLg0KDQojIyMgNC4gQ3JpbWUgUmF0ZSwgQWdlLCBhbmQgTWVkaWFuIFZhbHVlDQoNCioqRURBIFF1ZXN0aW9uLioqIEhvdyBkb2VzIHRoZSBjcmltZSBgY3JpbWAgcmF0ZSBjb3JyZWxhdGUgd2l0aCBgYWdlYCwgYW5kIGhvdyBhcmUgdGhlc2UgZmFjdG9ycyByZWxhdGVkIHRvIHdoZXRoZXIgdGhlIHByb3BlcnR5IHRyYWN0IGJvdW5kcyBDaGFybGVzIFJpdmVyIG9yIG5vdCAoc2VlOiBgY2hhc2AgdmFyaWFibGUpPw0KDQojIyMjIENyZWF0aW5nIGEgQnViYmxlIENoYXJ0IGluIFBsb3RseQ0KDQpIZXJlIGlzIHRoZSBjb21wbGV0ZSBjb2RlOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpwbG90X2x5KGRhdGEgPSBob3VzaW5nLCANCiAgICAgICAgeCA9IH5hZ2UsIA0KICAgICAgICB5ID0gfmNyaW0sIA0KICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCANCiAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywNCiAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gfm1lZHYvMywNCiAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IH5jaGFzKSwNCiAgICAgICAgdGV4dCA9IH5wYXN0ZSgiQ3JpbWUgUmF0ZToiLCBjcmltLCANCiAgICAgICAgICAgICAgICAgICAgICAiPGJyPkFnZToiLCBhZ2UsIA0KICAgICAgICAgICAgICAgICAgICAgICI8YnI+TWVkaWFuIFZhbHVlOiIsIG1lZHYsDQogICAgICAgICAgICAgICAgICAgICAgIjxicj5DaGFybGVzIFJpdmVyOiIsIGNoYXMpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkJ1YmJsZSBDaGFydCBvZiBDcmltZSBSYXRlIHZzLiBBZ2UiLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIkFnZSIpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkNyaW1lIFJhdGUiKSkNCg0KYGBgDQoNCkxvZy1zY2FsZXMgZm9yIGJvdGggYGNyaW1gIGFuZCBgYWdlYCBtaWdodCBiZSBvZiBzb21lIGhlbHAgaGVyZToNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcGxvdF9seShkYXRhID0gaG91c2luZywgDQogICAgICAgIHggPSB+bG9nKGFnZSksIA0KICAgICAgICB5ID0gfmxvZyhjcmltKSwgDQogICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIA0KICAgICAgICBtb2RlID0gJ21hcmtlcnMnLA0KICAgICAgICBtYXJrZXIgPSBsaXN0KHNpemUgPSB+bWVkdi8zLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gfmNoYXMpLA0KICAgICAgICB0ZXh0ID0gfnBhc3RlKCJDcmltZSBSYXRlOiIsIGNyaW0sIA0KICAgICAgICAgICAgICAgICAgICAgICI8YnI+QWdlOiIsIGFnZSwgDQogICAgICAgICAgICAgICAgICAgICAgIjxicj5NZWRpYW4gVmFsdWU6IiwgbWVkdiwNCiAgICAgICAgICAgICAgICAgICAgICAiPGJyPkNoYXJsZXMgUml2ZXI6IiwgY2hhcykpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiQnViYmxlIENoYXJ0IG9mIENyaW1lIFJhdGUgdnMuIEFnZSIsDQogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAibG9nKEFnZSkiKSwNCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJsb2coQ3JpbWUgUmF0ZSkiKSkNCmBgYA0KDQoqKkFuYWx5c2lzLioqIFRoZSBoaWdoZXN0IGNyaW1lIHJhdGVzIGFyZSBpbmRlZWQgZm91bmQgaW4gdGhlIGFyZWFzIHdoZXJlIG9sZGVzdCBwcm9wZXJ0aWVzIGFyZSBzaXR1YXRlZCB3aGljaCBzZWVtIHRvIGJlIHRoZSBjaGVhcGVzdCBhdCB0aGUgc2FtZSB0aW1lLiBUaGVyZSBpcyBubyBhcHBhcmVudCBldmlkZW5jZSB3aGV0aGVyIGJvdW5kaW5nIHRoZSBDaGFybGVzIFJpdmVyIGJhbmtzIG1ha2UgYW55IGRpZmZlcmVuY2UNCg0KIyMjIDUuIERpZmZlcmVuY2UgYmV0d2VlbiBgdHlwZWAgYW5kIGBtb2RlYCBhbmQgdGhlaXIgdXNhZ2UgaW4gYHBsb3RfbHkoKWANCg0KSW4gUGxvdGx5LCBwYXJ0aWN1bGFybHkgd2hlbiB1c2luZyB0aGUgYHBsb3RfbHkoKWAgZnVuY3Rpb24gaW4gUiwgdGhlIGB0eXBlYCBhbmQgYG1vZGVgIHBhcmFtZXRlcnMgc2VydmUgZGlzdGluY3Qgcm9sZXMgaW4gZGVmaW5pbmcgaG93IGRhdGEgaXMgdmlzdWFsaXplZDoNCg0KIyMjIyBgdHlwZWAgcGFyYW1ldGVyDQoNCi0gKipEZWZpbml0aW9uKio6IFRoZSBgdHlwZWAgcGFyYW1ldGVyIGluIGBwbG90X2x5KClgIHNwZWNpZmllcyB0aGUgdHlwZSBvZiBwbG90IG9yIGNoYXJ0IHRoYXQgeW91IHdhbnQgdG8gY3JlYXRlLiBUaGlzIGRldGVybWluZXMgdGhlIG92ZXJhbGwgdmlzdWFsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkYXRhLg0KDQotICoqQ29tbW9uIFR5cGVzKio6IFRoZXNlIGluY2x1ZGUgYHNjYXR0ZXJgLCBgYmFyYCwgYGJveGAsIGBoaXN0b2dyYW1gLCBgaGVhdG1hcGAsIGBzdXJmYWNlYCwgYHNjYXR0ZXIzZGAsIGBtZXNoM2RgLCBhbmQgb3RoZXJzLg0KDQotICoqVXNhZ2UgRXhhbXBsZSoqOiBJZiB5b3Ugd2FudCB0byBjcmVhdGUgYSBsaW5lIHBsb3QsIHlvdSB3b3VsZCBzZXQgYHR5cGUgPSAnc2NhdHRlcidgIGFuZCB0aGVuIHNwZWNpZnkgYG1vZGUgPSAnbGluZXMnYCAoZXhwbGFpbmVkIGJlbG93IHVuZGVyIGBtb2RlYCkuIEZvciBhIHNpbXBsZSBiYXIgY2hhcnQsIHlvdSB3b3VsZCB1c2UgYHR5cGUgPSAnYmFyJ2AuDQoNCiMjIyBgbW9kZWAgcGFyYW1ldGVyDQoNCi0gKipEZWZpbml0aW9uKio6IFRoZSBgbW9kZWAgcGFyYW1ldGVyIGlzIHVzZWQgcHJpbWFyaWx5IHdpdGggc2NhdHRlciBwbG90cyAoaW5jbHVkaW5nIGxpbmUgY2hhcnRzIGFuZCBidWJibGUgY2hhcnRzKSB0byBkZWZpbmUgaG93IHRoZSBkYXRhIHBvaW50cyBhcmUgY29ubmVjdGVkIG9yIHJlcHJlc2VudGVkIHZpc3VhbGx5IHdpdGhpbiB0aGUgZ2l2ZW4gYHR5cGVgLg0KDQotICoqQ29tbW9uIE1vZGVzKio6IEZvciBgdHlwZSA9ICdzY2F0dGVyJ2AsIGNvbW1vbiBgbW9kZWAgdmFsdWVzIGluY2x1ZGU6DQogIC0gYG1hcmtlcnNgOiBEaXNwbGF5cyBkYXRhIHBvaW50cyBhcyBpbmRpdmlkdWFsIG1hcmtlcnMgKGRvdHMpLg0KICAtIGBsaW5lc2A6IENvbm5lY3RzIGRhdGEgcG9pbnRzIHdpdGggbGluZXMuDQogIC0gYHRleHRgOiBEaXNwbGF5cyBkYXRhIHBvaW50cyBhcyB0ZXh0IGxhYmVscy4NCiAgLSBgbGluZXMrbWFya2Vyc2A6IFVzZXMgYm90aCBsaW5lcyBhbmQgbWFya2VycyB0byByZXByZXNlbnQgZGF0YSBwb2ludHMuDQogIC0gYGxpbmVzK21hcmtlcnMrdGV4dGA6IENvbWJpbmVzIGxpbmVzLCBtYXJrZXJzLCBhbmQgdGV4dCB0byBkaXNwbGF5IHRoZSBkYXRhLg0KLSAqKlVzYWdlIEV4YW1wbGUqKjogVG8gY3JlYXRlIGEgcGxvdCB0aGF0IHNob3dzIGJvdGggdGhlIGRhdGEgcG9pbnRzIGFuZCB0aGUgbGluZXMgY29ubmVjdGluZyB0aGVtLCB5b3Ugd291bGQgdXNlIGBwbG90X2x5KHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycpYC4NCg0KKipFeGFtcGxlIGluIFIqKg0KDQpIZXJl4oCZcyBhIHNpbXBsZSBleGFtcGxlIGRlbW9uc3RyYXRpbmcgdGhlIHVzZSBvZiBgdHlwZWAgYW5kIGBtb2RlYCBpbiBgcGxvdF9seSgpYDoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyBTYW1wbGUgZGF0YQ0KZGF0YSA8LSBkYXRhLmZyYW1lKA0KICB4ID0gMToxMDAsDQogIHkgPSBzb3J0KHJ1bmlmKDEwMCwgLTUwLCA1MCkpDQopDQoNCiMgTGluZSBwbG90DQpwbG90X2x5KGRhdGEsIA0KICAgICAgICB4ID0gfngsIA0KICAgICAgICB5ID0gfnksIA0KICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCANCiAgICAgICAgbW9kZSA9ICdsaW5lcycpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnTGluZSBQbG90JykNCmBgYA0KDQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCiMgU2NhdHRlciBwbG90IHdpdGggbWFya2Vycw0KcGxvdF9seShkYXRhLCANCiAgICAgICAgeCA9IH54LCANCiAgICAgICAgeSA9IH55LCANCiAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgDQogICAgICAgIG1vZGUgPSAnbWFya2VycycpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnU2NhdHRlciBQbG90IHdpdGggTWFya2VycycpDQpgYGANCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyBTY2F0dGVyIHBsb3Qgd2l0aCBtYXJrZXJzIGFuZCBsaW5lcw0KcGxvdF9seShkYXRhLCANCiAgICAgICAgeCA9IH54LCANCiAgICAgICAgeSA9IH55LCANCiAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgDQogICAgICAgIG1vZGUgPSAnbWFya2VycytsaW5lcycpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnU2NhdHRlciBQbG90IHdpdGggTWFya2VycyBhbmQgTGluZXMnKQ0KYGBgDQoNCkZvciBhIHNpbXBsZSBiYXIgcGxvdCB3ZSBjYW4gdXNlIGB0eXBlPSdiYXInYCB3aXRoIGBtb2RlPSdsaW5lcydgOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQojIFNjYXR0ZXIgcGxvdCB3aXRoIGJhcnMNCnBsb3RfbHkoZGF0YSwgDQogICAgICAgIHggPSB+eCwgDQogICAgICAgIHkgPSB+eSwgDQogICAgICAgIHR5cGUgPSAnYmFyJywgDQogICAgICAgIG1vZGUgPSAnbGluZXMnKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gJ1NpbXBsZSBCYXQgUGxvdCcpDQpgYGANCg0KIyMjIDYuIEEgcHJhY3RpY2FsIGludHJvZHVjdGlvbiB0byBBL0IgdGVzdGluZw0KDQojIyMjIDYuMSBUZXN0aW5nIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdHdvIG1lYW5zOiAqKnQtdGVzdCoqDQoNClRoZSAqKnQtdGVzdCoqIGlzIGEgc3RhdGlzdGljYWwgdGVzdCB0aGF0IGlzIHVzZWQgdG8gZGV0ZXJtaW5lIHdoZXRoZXIgdGhlcmUgaXMgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIG1lYW5zIG9mIHR3byBncm91cHMuIEl0IGlzIGNvbW1vbmx5IHVzZWQgd2hlbiB5b3Ugd2FudCB0byBjb21wYXJlIHRoZSBhdmVyYWdlIHBlcmZvcm1hbmNlLCBtZWFzdXJlbWVudHMsIG9yIG91dGNvbWVzIGJldHdlZW4gdHdvIGdyb3VwcyB1bmRlciBkaWZmZXJlbnQgY29uZGl0aW9ucyBvciB0cmVhdG1lbnRzLiBUaGUgdC10ZXN0IHByb3ZpZGVzIGEgd2F5IHRvIGNoZWNrIGlmIHRoZSBkaWZmZXJlbmNlcyBvYnNlcnZlZCBpbiB0aGUgZGF0YSBhcmUgbGlrZWx5IHRvIGJlIGdlbnVpbmVseSByZWZsZWN0aW5nIGEgZGlmZmVyZW5jZSBpbiB0aGUgcG9wdWxhdGlvbiwgb3IgaWYgdGhleSBjb3VsZCBqdXN0IGJlIGR1ZSB0byByYW5kb20gdmFyaWF0aW9uLg0KDQpUaGVyZSBhcmUgc2V2ZXJhbCB0eXBlcyBvZiB0LXRlc3RzLCBidXQgdGhlIG1vc3QgY29tbW9uIGFyZToNCg0KLSAqKkluZGVwZW5kZW50IHNhbXBsZXMgdC10ZXN0LioqIFRoaXMgaXMgdXNlZCB3aGVuIGNvbXBhcmluZyB0d28gZGlmZmVyZW50IGdyb3VwcyBvZiBzdWJqZWN0cyAoZS5nLiwgcGVvcGxlIHJlY2VpdmluZyB0d28gZGlmZmVyZW50IHR5cGVzIG9mIHRyZWF0bWVudCkuDQotICoqUGFpcmVkIHNhbXBsZSB0LXRlc3QuKiogVGhpcyBpcyB1c2VkIHdoZW4gY29tcGFyaW5nIHR3byB2YWx1ZXMgZnJvbSB0aGUgc2FtZSBncm91cCBhdCBkaWZmZXJlbnQgdGltZXMgKGUuZy4sIGJlZm9yZSBhbmQgYWZ0ZXIgdHJlYXRtZW50IGZvciB0aGUgc2FtZSBncm91cCBvZiBwYXRpZW50cykuDQoNCkZvciBvdXIgYW5hbHlzaXMsIHdlJ2xsIGZvY3VzIG9uIHRoZSBpbmRlcGVuZGVudCBzYW1wbGVzIHQtdGVzdCBhcyB3ZSBhcmUgY29tcGFyaW5nIHR3byBkaWZmZXJlbnQgc3Vic2V0cyBvZiBkYXRhOg0KDQotICoqUS4qKiBJcyB0aGVyZSBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBpbiB0aGUgbWVkaWFuIHZhbHVlcyBvZiBob21lcyBiZXR3ZWVuIGFyZWFzIHdpdGggY3JpbWUgcmF0ZXMgYWJvdmUgdGhlIG1lZGlhbiBjb21wYXJlZCB0byB0aG9zZSB3aXRoIGNyaW1lIHJhdGVzIGF0IG9yIGJlbG93IHRoZSBtZWRpYW4gaW4gdGhlIEJvc3RvbiBob3VzaW5nIG1hcmtldD8NCg0KTGV0J3MgbW92ZSB0aHJvdWdoIHRoZSBzdGVwcyBvZiBvdXIgYW5hbHlzaXM6DQoNCi0gQ2FsY3VsYXRlIHRoZSBtZWRpYW4gb2YgdGhlIGBjcmltYCAoY3JpbWUgcmF0ZSkgdmFyaWFibGUuDQotIFNwbGl0IHRoZSBkYXRhIHNldCBiYXNlZCBvbiB3aGV0aGVyIGBjcmltYCBpcyBncmVhdGVyIHRoYW4gb3IgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIGl0cyBtZWRpYW4uDQotIENvbmR1Y3QgYW4gaW5kZXBlbmRlbnQgdC10ZXN0IG9uIHRoZSBgbWVkdmAgKG1lZGlhbiB2YWx1ZSBvZiBvd25lci1vY2N1cGllZCBob21lcykgYmV0d2VlbiB0aGUgdHdvIGdyb3Vwcy4NCi0gUmVwb3J0IGFuZCBpbnRlcnByZXQgdGhlIHJlc3VsdHMuDQoNCkZpcnN0LCB3ZSB3aWxsIGxvYWQgdGhlIGRhdGEsIGNhbGN1bGF0ZSB0aGUgbmVjZXNzYXJ5IHN0YXRpc3RpY3MsIGFuZCBwZXJmb3JtIHRoZSB0LXRlc3QuDQoNCkZpbmQgdGhlIG1lZGlhbiBvZiBgY3JpbWA6DQoNCmBgYHtyIGVjaG89VFJVRX0NCiMgQ2FsY3VsYXRlIHRoZSBtZWRpYW4gb2YgdGhlICdjcmltJyB2YXJpYWJsZQ0KbWVkaWFuX2NyaW0gPC0gbWVkaWFuKGhvdXNpbmckY3JpbSkNCnByaW50KG1lZGlhbl9jcmltKQ0KYGBgDQoNClVzZSBgZHBseXJgIHRvIGNyZWF0ZSB0d28gbmV3IGRhdGEgc2V0cyB0aGF0IHdlIG5lZWQ6DQoNCmBgYHtyIGVjaG89VFJVRX0NCiMgU3Vic2V0IHRoZSBkYXRhIGludG8gdHdvIGdyb3Vwcw0KbG93X2NyaW0gPC0gaG91c2luZyAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoY3JpbSA8PSBtZWRpYW5fY3JpbSkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KGNyaW0sIG1lZHYpDQoNCmhpZ2hfY3JpbSA8LSBob3VzaW5nICU+JSANCiAgZHBseXI6OmZpbHRlcihjcmltID4gbWVkaWFuX2NyaW0pICU+JSANCiAgZHBseXI6OnNlbGVjdChjcmltLCBtZWR2KQ0KDQpoZWFkKGxvd19jcmltKQ0KaGVhZChoaWdoX2NyaW0pDQpgYGANCg0KUGVyZm9ybSB0LXRlc3QgaW4gUiB1c2luZyBgdC50ZXN0KClgOg0KDQpgYGB7ciBlY2hvPVRSVUV9DQojIFBlcmZvcm0gYW4gaW5kZXBlbmRlbnQgdC10ZXN0IG9uICdtZWR2Jw0KdF90ZXN0X3Jlc3VsdHMgPC0gdC50ZXN0KHggPSBsb3dfY3JpbSRtZWR2LCANCiAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gaGlnaF9jcmltJG1lZHYsDQogICAgICAgICAgICAgICAgICAgICAgICAgIyB2YXIuZXF1YWwgPSBUUlVFDQogICAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQojIFByaW50IHRoZSByZXN1bHRzDQpwcmludCh0X3Rlc3RfcmVzdWx0cykNCmBgYA0KDQpUaGlzIGNvZGUgd2lsbCBvdXRwdXQgdGhlIHJlc3VsdHMgb2YgdGhlIHQtdGVzdCwgd2hpY2ggaW5jbHVkZXMgdGhlIHQtc3RhdGlzdGljLCBkZWdyZWVzIG9mIGZyZWVkb20sIHAtdmFsdWUsIGFuZCBjb25maWRlbmNlIGludGVydmFsIG9mIHRoZSBkaWZmZXJlbmNlIGluIG1lYW5zLiANCg0KKipJbnRlcnByZXRhdGlvbjoqKg0KDQotIGBULXN0YXRpc3RpY2A6IFRoaXMgdmFsdWUgaW5kaWNhdGVzIHRoZSBjYWxjdWxhdGVkIGRpZmZlcmVuY2UgaW4gbWVhbnMgYmV0d2VlbiB0aGUgdHdvIGdyb3VwcyAobG93IGNyaW1lIHJhdGUgYW5kIGhpZ2ggY3JpbWUgcmF0ZSBhcmVhcykgcmVsYXRpdmUgdG8gdGhlIHNwcmVhZCBvciB2YXJpYWJpbGl0eSBvZiB0aGVpciBzY29yZXMuIEEgaGlnaGVyIHQtc3RhdGlzdGljIGluZGljYXRlcyBhIGdyZWF0ZXIgZGlmZmVyZW5jZSBiZXR3ZWVuIGdyb3Vwcy4NCg0KLSBgUC12YWx1ZWA6IFRoZSBwLXZhbHVlIGlzIGV4dHJlbWVseSBzbWFsbCwgZmFyIGxlc3MgdGhhbiAwLjA1LCB3aGljaCBpcyBhIGNvbW1vbiB0aHJlc2hvbGQgZm9yIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSBpbiBzb2NpYWwgc2NpZW5jZXMuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgZGlmZmVyZW5jZXMgaW4gbWVkaWFuIGhvbWUgdmFsdWVzIGJldHdlZW4gYXJlYXMgd2l0aCBjcmltZSByYXRlcyBoaWdoZXIgdGhhbiB0aGUgbWVkaWFuIGFuZCB0aG9zZSB3aXRoIGNyaW1lIHJhdGVzIGxvd2VyIG9yIGVxdWFsIHRvIHRoZSBtZWRpYW4gYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuDQoNCioqQ29uY2x1c2lvbjoqKg0KDQpCYXNlZCBvbiB0aGUgdC10ZXN0LCB3ZSBjYW4gY29uY2x1ZGUgdGhhdCB0aGVyZSBpcyBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBpbiB0aGUgbWVkaWFuIHZhbHVlcyBvZiBob21lcyBiZXR3ZWVuIGFyZWFzIHdpdGggcmVsYXRpdmVseSBoaWdoZXIgY3JpbWUgcmF0ZXMgY29tcGFyZWQgdG8gdGhvc2Ugd2l0aCBsb3dlciBjcmltZSByYXRlcy4gVGhpcyBpbXBsaWVzIHRoYXQsIG9uIGF2ZXJhZ2UsIGFyZWFzIHdpdGggbG93ZXIgY3JpbWUgcmF0ZXMgdGVuZCB0byBoYXZlIGhpZ2hlciBtZWRpYW4gaG9tZSB2YWx1ZXMuIFRoaXMgcmVzdWx0IGlzIHF1aXRlIGludHVpdGl2ZSBhcyBzYWZlciBuZWlnaGJvcmhvb2RzIGFyZSBnZW5lcmFsbHkgbW9yZSBkZXNpcmFibGUgYW5kIGNhbiBkcml2ZSBoaWdoZXIgcHJvcGVydHkgdmFsdWVzLiBUbyBpbGx1c3RyYXRlOg0KDQpgYGB7ciBlY2hvPVRSVUV9DQpob3VzaW5nICU+JSANCiAgZHBseXI6OnNlbGVjdChjcmltLCBtZWR2KSAlPiUgDQogIGRwbHlyOjptdXRhdGUobWVkX2NyaW0gPSBjcmltID4gbWVkaWFuKGNyaW0pKSAlPiUgDQogIGRwbHlyOjpzZWxlY3QobWVkX2NyaW0sIG1lZHYpICU+JSANCiAgZHBseXI6Omdyb3VwX2J5KG1lZF9jcmltKSAlPiUgDQogIGRwbHlyOjpzdW1tYXJpc2UobWVhbl9tZWR2ID0gbWVhbihtZWR2KSwgDQogICAgICAgICAgICAgICAgICAgbWVkaWFuX21lZHYgPSBtZWRpYW4obWVkdikpDQpgYGANCg0KIyMjIFZpZGVvcw0KDQotIFdhdGNoIFtEZXNjcmlwdGl2ZSBTdGF0aXN0aWNzLCBEYXZpZCBDYXVnaGxpbl0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1XQ0M0SVhhdml0cykNCi0gV2F0Y2ggW1ZhcmlhbmNlIC0gQ2xlYXJseSBFeHBsYWluZWQsIFN0ZXZlbiBCcmFkYnVybl0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj14MHJtVVhXdFNTOCkNCi0gV2F0Y2ggW0hvdyBUbyBDYWxjdWxhdGUgVGhlIFN0YW5kYXJkIERldmlhdGlvbiwgU3RldmVuIEJyYWRidXJuXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PXgwcm1VWFd0U1M4KQ0KDQojIyMgUiBNYXJrZG93bg0KDQpbUiBNYXJrZG93bl0oaHR0cHM6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vKSBpcyB3aGF0IEkgaGF2ZSB1c2VkIHRvIHByb2R1Y2UgdGhpcyBiZWF1dGlmdWwgTm90ZWJvb2suIFdlIHdpbGwgbGVhcm4gbW9yZSBhYm91dCBpdCBuZWFyIHRoZSBlbmQgb2YgdGhlIGNvdXJzZSwgYnV0IGlmIHlvdSBhbHJlYWR5IGZlZWwgcmVhZHkgdG8gZGl2ZSBkZWVwLCBoZXJlJ3MgYSBib29rOiBbUiBNYXJrZG93bjogVGhlIERlZmluaXRpdmUgR3VpZGUsIFlpaHVpIFhpZSwgSi4gSi4gQWxsYWlyZSwgR2FycmV0dCBHcm9sZW11bmRzLl0oaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duLykgDQoNCg0KKioqDQpHb3JhbiBTLiBNaWxvdmFub3ZpxIcNCg0KRGF0YUtvbGVrdGl2LCAyMDI0Lg0KDQpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tDQoNCiFbXShfaW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQpMaWNlbnNlOiBbR1BMdjNdKGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy9ncGwtMy4wLnR4dCkNClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLg0KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuDQpZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhbG9uZyB3aXRoIHRoaXMgTm90ZWJvb2suIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4NCg0KKioqDQoNCg==